package com.weifly.weistock.trade.trade.jni;

import com.sun.jna.Native;
import com.weifly.weistock.core.common.StockException;
import com.weifly.weistock.core.event.StockEventEnum;
import com.weifly.weistock.trade.config.AccountConfigDto;
import com.weifly.weistock.trade.trade.SendOrderInfo;
import com.weifly.weistock.trade.trade.StockOrderInfo;
import com.weifly.weistock.core.common.Result;
import com.weifly.weistock.trade.enums.OrderStatusEnum;
import com.weifly.weistock.trade.enums.SendCategory;
import com.weifly.weistock.trade.enums.TradeResultEnum;
import com.weifly.weistock.trade.trade.StockTradeService;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;

/**
 * 基于dll的股票交易
 *
 * @author weifly
 * @since 2019/1/28
 */
public class JniStockTradeService extends StockTradeService {

    private Logger log = LoggerFactory.getLogger(JniStockTradeService.class);

    private TdxLibrary tdxLibrary; // 本地dll库
    private int clientID = -1; // 登录后的唯一标识
    private byte[] result=new byte[1024*1024]; // 调用返回结果
    private byte[] errInfo=new byte[256]; // 调用出错信息
    private boolean open = false; // 打开tdx，则为true
    private boolean login = false; // 登录成功，则为true

    private TdxLibrary takeLibrary(){
        // 加载dll
        if(this.tdxLibrary==null){
            this.tdxLibrary = (TdxLibrary) Native.loadLibrary("trade",TdxLibrary.class);
        }

        // 打开tdx
        if(!this.open){
            this.tdxLibrary.OpenTdx();
            this.open = true;
        }

        //登录
        if(!this.login){
            AccountConfigDto config = this.takeAccountConfig();
            String txPassword = StringUtils.isBlank(config.getTxPassword()) ? "" : config.getTxPassword();
            this.clientID = this.tdxLibrary.Logon(
                    config.getServerIp(),
                    config.getServerPort().shortValue(),
                    config.getVersion(),
                    config.getYybID().shortValue(),
                    config.getAccountNo(),
                    config.getTradeAccount(),
                    config.getJyPassword(),
                    txPassword,
                    this.errInfo);
            if (this.clientID==-1) {
                String errMsg = Native.toString(this.errInfo, "GBK");
                throw new StockException("登录失败：" + errMsg);
            }
            this.login = true;
        }

        return this.tdxLibrary;
    }

    public List<StockOrderInfo> getOrderList() {
        TdxLibrary lib = this.takeLibrary();
        if(lib==null){
            log.error("无法获得dll引用");
            return null;
        }

        // 查询委托信息
        lib.QueryData(this.clientID, 2, this.result, this.errInfo);

        String err = Native.toString(this.errInfo, "GBK");
        if(err!=null && err.length()>0){
            log.error("查询委托信息出错：" + err);
            return null;
        }

        String queryOrderStr = Native.toString(this.result, "GBK");
        List<StockOrderInfo> orderList = new ArrayList<>();
        if(queryOrderStr==null || queryOrderStr.isEmpty()){
            log.info("没有委托信息");
            return orderList;
        }

        String[] sendOrders = queryOrderStr.split("\n");
        for(int i=0;i<sendOrders.length;i++){
            String oneOrder = sendOrders[i];
            if(oneOrder==null || oneOrder.trim().isEmpty()){
                continue;
            }
            String[] orderFields = oneOrder.split("\t");
            if(orderFields.length<2){
                continue;
            }
            if(orderFields[0].indexOf("委托时间")!=-1){
                continue;
            }
            StockOrderInfo orderInfo = new StockOrderInfo();
            this.setOrderTime(orderInfo, orderFields[0]); // 委托时间
            orderInfo.setOrderNumber(orderFields[1]); // 申请编号
            orderInfo.setStockCode(orderFields[2]); // 证券代码
            orderInfo.setStockName(orderFields[3]); // 证券名称
            this.setBuyOrSell(orderInfo, orderFields[4]); // 买卖标志
            // 5 买卖
            // 6 委托类型
            this.setOrderStatus(orderInfo, orderFields[7]); // 委托状态
            orderInfo.setEntrustPrice(Double.valueOf(orderFields[8])); // 委托价格
            orderInfo.setEntrustSize(Double.valueOf(orderFields[9]).intValue()); // 委托数量
            // 10 成交价格
            // 11 成交数量
            // 12 成交金额
            // 13 已撤数量
            // 14 撤单标志
            // 15 业务名称
            // 16 帐号类别
            // 17 股东代码
            // 18 资金帐号
            // 19 委托编号
            // 20 返回信息
            // 21 可撤单标志
            // 22 参数
            // 23 保留信息
            orderList.add(orderInfo);
        }
        //this.printOrderList(orderList);
        return orderList;
    }

    public Result sendOrder(SendOrderInfo sendOrderInfo) {
        if(sendOrderInfo.getCategory()==null){
            return Result.createErrorResult("委托种类不能为空", TradeResultEnum.PARAM_ERROR);
        }
        if(sendOrderInfo.getStockCode()==null){
            return Result.createErrorResult("证券代码不能为空", TradeResultEnum.PARAM_ERROR);
        }
        if(sendOrderInfo.getEntrustPrice()==null){
            return Result.createErrorResult("委托价格不能为空", TradeResultEnum.PARAM_ERROR);
        }
        if(sendOrderInfo.getEntrustSize()==null){
            return Result.createErrorResult("委托数量不能为空", TradeResultEnum.PARAM_ERROR);
        }

        TdxLibrary lib = this.takeLibrary();
        if(lib==null){
            log.error("无法获得dll引用");
            return Result.createErrorResult("无法获得dll引用", TradeResultEnum.PARAM_ERROR);
        }

        // 委托下单
        int category = Integer.parseInt(sendOrderInfo.getCategory().getValue());
        String stockCode = sendOrderInfo.getStockCode();
        float price = sendOrderInfo.getEntrustPrice().floatValue();
        int quantity = sendOrderInfo.getEntrustSize();
        lib.SendOrder(this.clientID, category, 0, null, stockCode, price, quantity, this.result, this.errInfo);

        String err = Native.toString(this.errInfo, "GBK");
        if(err!=null && err.length()>0){
            log.error("下单失败：" + err);
            if(err.startsWith("[251005][证券可用数量不足]")){
                return Result.createErrorResult(err, TradeResultEnum.SELL_NO_MORE_STOCK);
            }
            return Result.createErrorResult(err, TradeResultEnum.UNKOWN);
        }

        String sendOrderResult = Native.toString(this.result, "GBK");
        if(sendOrderResult==null || sendOrderResult.trim().isEmpty()){
            log.info("发送请求后，无返回内容");
            return Result.createErrorResult("发送请求后，无返回内容", TradeResultEnum.UNKOWN);
        }

        // 正常返回：
        // 委托编号	批次号	返回信息	URL	备注	参数	保留信息
        // 114
        int orderNumber = -1;
        try{
            orderNumber = this.parseOrderNumber(sendOrderResult);
        }catch(Exception e){
            log.error("解析下单结果失败：" + sendOrderResult, e);
            return Result.createErrorResult("解析下单结果失败", TradeResultEnum.UNKOWN);
        }

        StockOrderInfo orderInfo = new StockOrderInfo();
        orderInfo.setStockCode(sendOrderInfo.getStockCode());
        orderInfo.setStockName(sendOrderInfo.getStockName());
        orderInfo.setOrderNumber(String.valueOf(orderNumber));
        orderInfo.setOrderStatus(OrderStatusEnum.status_5);
        orderInfo.setOrderTime(System.currentTimeMillis());
        orderInfo.setBuyOrSell(sendOrderInfo.getBuyOrSell());
        orderInfo.setEntrustPrice(sendOrderInfo.getEntrustPrice());
        orderInfo.setEntrustSize(sendOrderInfo.getEntrustSize());
        orderInfo.setCreditBuy(sendOrderInfo.isCreditBuy());
        return Result.createSuccessResult(orderInfo);
    }

    public Result cancelOrder(StockOrderInfo stockOrderInfo) {
        if(stockOrderInfo.getExchangeId()==null){
            return Result.createErrorResult("缺少交易所ID", TradeResultEnum.PARAM_ERROR);
        }
        if(stockOrderInfo.getOrderNumber()==null){
            return Result.createErrorResult("缺少委托编号", TradeResultEnum.PARAM_ERROR);
        }

        TdxLibrary lib = this.takeLibrary();
        if(lib==null){
            log.error("无法获得dll引用");
            return Result.createErrorResult("无法获得dll引用", TradeResultEnum.PARAM_ERROR);
        }

        // 撤销委托
        lib.CancelOrder(this.clientID, stockOrderInfo.getExchangeId(), stockOrderInfo.getOrderNumber(), this.result, this.errInfo);

        // [255097][订单不存在][entrust_no=150,branch_no=28,init_date=20190311,fund_account=XXXXXXXX]
        String err = Native.toString(this.errInfo, "GBK");
        if(err!=null && err.length()>0){
            log.error("撤单失败：" + err);
            return Result.createErrorResult(err, TradeResultEnum.UNKOWN);
        }

        String cancelOrderResult = Native.toString(this.result, "GBK");
        if(cancelOrderResult==null || cancelOrderResult.trim().isEmpty()){
            log.info("发送请求后，无返回内容");
            return Result.createErrorResult("发送请求后，无返回内容", TradeResultEnum.UNKOWN);
        }

        // 正常返回：
        // 委托编号	返回信息	保留信息
        // 143
        int orderNumber = -1;
        try{
            orderNumber = this.parseOrderNumber(cancelOrderResult);
        }catch(Exception e){
            log.error("解析下单结果失败：" + cancelOrderResult, e);
            return Result.createErrorResult("解析下单结果失败", TradeResultEnum.UNKOWN);
        }

        return Result.createSuccessResult(String.valueOf(orderNumber));
    }

    private int parseOrderNumber(String sendOrderResult){
        int orderNumber = -1;
        String[] sendOrders = sendOrderResult.split("\n");
        if(sendOrders.length<2){
            return orderNumber;
        }
        String[] orderFields = sendOrders[1].split("\t");
        if(orderFields.length<1){
            return orderNumber;
        }
        return Integer.parseInt(orderFields[0]);
    }

    public static void main(String[] args){
        JniStockTradeService tradeService = new JniStockTradeService();
        tradeService.getOrderList();

        SendOrderInfo sendOrderInfo = new SendOrderInfo();
        sendOrderInfo.setCategory(SendCategory.category_2);
        sendOrderInfo.setStockCode("510300");
        sendOrderInfo.setEntrustPrice(3.157);
        sendOrderInfo.setEntrustSize(1000);

        tradeService.sendOrder(sendOrderInfo);
    }

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        StockEventEnum eventEnum = StockEventEnum.findStockEvent(evt.getPropertyName());
        if(StockEventEnum.UPDATE_ACCOUNT.equals(eventEnum)){
            // 更新账户了，退出登录
            this.logoff();
        }
    }

    // 退出登录
    private void logoff(){
        if(!this.login){
            return;
        }

        log.info("已变更账户，退出登录");
        TdxLibrary lib = this.takeLibrary();
        lib.Logoff(this.clientID);
        this.clientID = -1;
        this.login = false;
    }
}
